import re
import threading
import tkinter

import _tkinter
# import keyboard
from pynput import keyboard as pynput_keyboard
from tkinter import font, Toplevel
from mem import *
from function_tools import *


class Tick:
  def __init__(self, rootForm: 'Form', sourceTKForm, tick, func, *args):
    self.rootForm = rootForm
    self.sourceTKForm = sourceTKForm
    self.tick = tick
    self.func = func
    self.args = args
    self.rootForm.tkForm.after(tick, self.Do)

  def Do(self):
    if self.sourceTKForm.winfo_viewable():
      # print('Do', self.func, self.args)
      self.OnCallFunc(self.rootForm.CallFunc(self.func, *self.args))
    self.rootForm.tkForm.after(self.tick, self.Do)

  def OnCallFunc(self, result):
    pass


class MapPosUpdateTick(Tick):
  def __init__(self, rootForm: 'Form', sourceTKForm, canvas: tkinter.Canvas, tick, color, func, *args,
               posTransform=None):
    super().__init__(rootForm, sourceTKForm, tick, func, *args)
    self.color = color
    self.canvas = canvas
    self.posTransform = posTransform
    self.point = None

  def OnCallFunc(self, result):
    if result is None:
      if debugLevel > 1:
        print(Fore.YELLOW, f'[warn]MapPosUpdateTick.通过{self.func}({self.args})获取坐标失败。')
      return
      # result = (10, 10, 10)
    # print('OnCallFunc=', result)
    if self.posTransform is not None:
      x, y, z = self.posTransform(result)
    else:
      x, y, z = result
    # print('x, y, z=', x, y, z)
    if self.point is None:
      # 创建新元素标识自己
      self.point = self.canvas.create_oval(
        x - 2, y - 2, x + 2, y + 2, fill=self.color, outline=self.color
      )
    else:
      # 更新元素位置
      self.canvas.coords(self.point, x - 2, y - 2, x + 2, y + 2)


class Form(Mem):
  def __init__(self, gameName):
    super().__init__(gameName)
    self.gameName = gameName
    self.hotkeys = {}
    self._nowMenu = 'main'
    self.lastMenu = 'main'
    self._selectItemId = 0
    self.controlsFunc = []
    self.uiRoot = {}
    self._visible = True
    self._layout = {}
    self.childForms = {}
    self.onDownKeys = set()
    self.tkForm = tkinter.Tk()
    self.font = font.Font(family='微软雅黑', size=16, weight='bold')
    self.tkForm.title(f'Harry Cheat On {gameName}')
    self.tkForm.attributes("-topmost", True)
    self.tkForm.overrideredirect(True)
    self.tkForm.geometry("+100+0")
    self.formHotkeys = {
      104: lambda: self.MoveSelection(-1),
      98: lambda: self.MoveSelection(1),
      101: lambda: self.CallControlsFunc(self.selectItemId),
      96: lambda: self.ChangeVisible() if self.nowMenu == 'main' else self.ChangeMenu(self.lastMenu),
    }
    self.RegisterFormHotkeys()
    self.UpdateUi()

  def RegisterFormHotkeys(self):
    def Listen():
      with pynput_keyboard.Listener(on_press=self.OnKeyPress, on_release=self.OnKeyRelease) as listener:
        listener.join()

    listener_thread = threading.Thread(target=Listen, daemon=True)
    listener_thread.start()

  def MapSH(self, left, top, width, height, posConfigKey, showAllPos=None):
    """
    地图显示/隐藏
    showAllPos: List[tick频率(毫秒), List[颜色, 获取坐标列表的函数, *函数的参数], List[颜色, 获取坐标列表的函数2, *函数的参数]...]
    """
    if 'map' not in self.childForms:
      childForm = Toplevel(self.tkForm, bg=self.layout.get('map_bg', 'white'))
      childForm.attributes("-topmost", True)
      childForm.overrideredirect(True)
      childForm.geometry(f'{width}x{height}+{left}+{top}')
      childForm.attributes('-alpha', self.layout.get('alpha', 0.6))
      canvas = tkinter.Canvas(childForm, bg=self.layout.get('map_bg', 'white'), highlightthickness=0)
      minX = min([pos[0] for pos in self.allConfig[posConfigKey]])
      minY = min([pos[1] for pos in self.allConfig[posConfigKey]])
      minZ = min([pos[2] for pos in self.allConfig[posConfigKey]])
      maxX = max([pos[0] for pos in self.allConfig[posConfigKey]])
      maxY = max([pos[1] for pos in self.allConfig[posConfigKey]])
      maxZ = max([pos[2] for pos in self.allConfig[posConfigKey]])
      fullX = maxX - minX
      fullY = maxY - minY
      fullZ = maxZ - minZ
      gradient = self.layout.get('map_gradient', [(0, 0, 255), (255, 0, 0), (255, 255, 0)])

      def PosTransform(p):
        return (p[0] - minX) / fullX * width, height - (p[1] - minY) / fullY * height, (p[2] - minZ) / fullZ

      for pos in self.allConfig[posConfigKey]:
        x, y, z = PosTransform(pos)
        color = RGBToHex(get_gradient_color(z, gradient))
        # print('color=', color, z)
        canvas.create_rectangle(
          x - 1, y - 1, x + 1, y + 1, fill=color, outline=color
        )
      canvas.pack(fill=tkinter.BOTH, expand=True)
      self.childForms['map'] = childForm
      if showAllPos is not None:
        for funcInfo in showAllPos[1:]:
          MapPosUpdateTick(
            self, childForm, canvas, showAllPos[0], funcInfo[0], funcInfo[1], *funcInfo[2:], posTransform=PosTransform
          )
    else:
      print("self.childForms['map'].winfo_viewable()=", self.childForms['map'].winfo_viewable())
      if self.childForms['map'].winfo_viewable():
        self.childForms['map'].withdraw()
      else:
        self.childForms['map'].deiconify()

  def OnKeyRelease(self, key):
    if not hasattr(key, 'name'):
      return
    self.onDownKeys.remove(key.name)

  @Test
  def OnKeyPress(self, key):
    def Check(keyCode):
      hotkey = frozenset([keyCode])
      if hotkey in self.hotkeys:
        self.hotkeys[hotkey]()
        return
      # print('self.hotkeys=', self.hotkeys)
      for k in self.onDownKeys:
        hotkey = frozenset((k, keyCode))
        # print('hotkey=', hotkey)
        if hotkey in self.hotkeys:
          self.hotkeys[hotkey]()
          continue
    if not hasattr(key, 'vk'):
      self.onDownKeys.add(key.name)
      Check(key.name)
      return
    if self.visible and key.vk in self.formHotkeys:
      self.formHotkeys[key.vk]()
      return
    Check(key.vk)

  def MoveSelection(self, move):
    self.selectItemId += move

  def ChangeMenu(self, menu):
    self.nowMenu = menu

  @Test
  def CallControlsFunc(self, index):
    print(Fore.BLUE, f'[info]CallControlsFunc.{self.nowMenu}.{index}', Style.RESET_ALL)
    self.controlsFunc[index]()
    self.UpdateUi()

  @property
  def layout(self):
    if not self._layout:
      with open(f'{self.gameName}/layout.json', encoding='utf8') as f:
        self._layout = json.loads(f.read())
    return self._layout

  @property
  def nowMenu(self):
    return self._nowMenu

  @nowMenu.setter
  def nowMenu(self, value):
    self.lastMenu = self._nowMenu
    self._nowMenu = value
    self.selectItemId = 0

  @property
  def selectItemId(self):
    return self._selectItemId

  @selectItemId.setter
  def selectItemId(self, value):
    self._selectItemId = max(0, min(len(self.controlsFunc) - 1, value))
    self.UpdateUi()

  @property
  def visible(self):
    return self._visible

  @visible.setter
  def visible(self, value):
    self._visible = value
    if value:
      self.tkForm.deiconify()
      # self.RegisterFormHotkeys()
    else:
      # self.UnregisterFormHotKeys()
      self.tkForm.withdraw()

  def ReloadLayout(self):
    self._layout.clear()
    self.uiRoot.clear()
    # self.UpdateUi()

  def ChangeVisible(self):
    self.visible = not self.visible

  def ClearControl(self):
    self.controlsFunc.clear()
    for control in self.tkForm.winfo_children():
      control.destroy()

  def ClearHotkeys(self):
    # for hotkey in self.hotkeys:
    #   keyboard.remove_hotkey(hotkey)
    self.hotkeys.clear()

  def Nothing(self):
    pass

  def Exit(self):
    self.tkForm.quit()
    self.tkForm.update()

  def UpdateUi(self):
    # print('UpdateUi')
    layout = self.layout
    if self.nowMenu not in layout['menu']:
      return
    if self.nowMenu not in self.uiRoot:
      self.UpdateLayout()
      return
    if len(self.uiRoot[self.lastMenu]) != len(layout['menu'][self.nowMenu]):
      self.UpdateLayout()
      return
    # print('self.uiRoot=', self.uiRoot)
    try:
      for i, controlVarDict in enumerate(self.uiRoot[self.nowMenu]):
        controlDefine = controlVarDict['controlDefine']
        text = self.GetTextByCode(controlDefine['text'], controlDefine.get('hotkey'))
        controlVarDict['text'].set(text)
        controlVarDict['label'].config(
          bg=layout['background'] if i != self.selectItemId else layout['highlight_background']
        )
        controlVarDict['label'].config(
          fg=layout['foreground'] if i != self.selectItemId else layout['highlight_color']
        )
    except _tkinter.TclError:
      self.UpdateLayout()

  def GetTextByCode(self, text, hotkey=None):
    codes = re.findall(r'\$(.*)\$', text)
    if codes:
      try:
        text = re.sub(r'\$.*?\$', eval(codes[0]), text)
      except Exception as e:
        if debugLevel > 0:
          print(Fore.RED, f'[错误]GetTextByCode.{self.nowMenu}.{text}.执行“{codes[0]}”出错：{e}', Style.RESET_ALL)
        text = re.sub(r'\$.*?\$', '*错误*', text)
    if hotkey is not None:
      text = f'{text} ({hotkey.upper()})'
    return text

  def UpdateLayout(self):
    layout = self.layout
    if self.nowMenu not in layout['menu']:
      return
    if self.nowMenu not in self.uiRoot:
      self.uiRoot[self.nowMenu] = []
    self.ClearControl()
    self.ClearHotkeys()
    heightRows = 1
    # 初始化标题栏
    label = tkinter.Label(
      self.tkForm, text=layout['title']['text'], height=1, font=self.font,
      foreground=layout['title']['foreground'], background=layout['title']['background']
    )
    label.pack(fill=tkinter.X)
    for menuName, menuConfig in layout['menu'].items():
      if self.nowMenu == menuName:
        for i, controlDefine in enumerate(menuConfig):
          text = self.GetTextByCode(controlDefine['text'], controlDefine.get('hotkey'))
          controlVarDict = {
            'text': tkinter.StringVar(value=text),
            'controlDefine': controlDefine,
          }
          label = tkinter.Label(
            self.tkForm, textvariable=controlVarDict['text'], height=controlDefine.get('height', 1), justify='left',
            anchor='w',
            font=self.font,
            foreground=layout['foreground'] if i != self.selectItemId else layout['highlight_color'],
            background=layout['background'] if i != self.selectItemId else layout['highlight_background']
          )
          heightRows += controlDefine.get('height', 1)
          self.controlsFunc.append(
            lambda c=controlDefine: getattr(self, c.get('func', 'Nothing'))(*c.get('args', []))
          )
          label.pack(fill=tkinter.X)
          controlVarDict['label'] = label
          # print('label=', label)
          self.uiRoot[self.nowMenu].append(controlVarDict)

      for controlDefine in menuConfig:
        if 'hotkey' in controlDefine:
          hotkey = frozenset(StrToKeys(controlDefine['hotkey']))
          # keyboard.add_hotkey(
          #   hotkey, lambda c=controlDefine: self.CallFunc(c.get('func', 'Nothing'), *c.get('args', []))
          # )
          self.hotkeys[hotkey] = lambda c=controlDefine: self.CallFunc(c.get('func', 'Nothing'), *c.get('args', []))

    if "tail" in layout:
      # 初始化底栏
      heightRows += 1
      label = tkinter.Label(
        self.tkForm, text=layout['tail']['text'], height=1, font=self.font,
        foreground=layout['tail']['foreground'], background=layout['tail']['background']
      )
      label.pack(fill=tkinter.X)
    self.tkForm.geometry(f'{layout["width"]}x{heightRows * 33}')
    self.tkForm.attributes('-alpha', layout.get('alpha', 0.6))

  @Test
  def CallFunc(self, func, *args):
    return getattr(self, func)(*args)


if __name__ == '__main__':
  form = Form('gta5')
  form.tkForm.mainloop()
